package xin.marcher.module.evaluate.job;

import com.alibaba.fastjson.JSON;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import xin.marcher.module.common.constants.evaluate.EvaluateRedisKeyConstants;
import xin.marcher.module.common.core.PageResult;
import xin.marcher.module.common.enums.YesOrNoEnum;
import xin.marcher.module.common.exception.BaseBizException;
import xin.marcher.module.common.redis.RedisLock;
import xin.marcher.module.common.utils.DateFormatUtil;
import xin.marcher.module.evaluate.domain.entity.HandledItemCollectionDO;
import xin.marcher.module.evaluate.domain.entity.ItemCollectionDO;
import xin.marcher.module.evaluate.domain.entity.ItemScoreWeightConfigurationDO;
import xin.marcher.module.evaluate.domain.entity.ShopScoreWeightConfigurationDO;
import xin.marcher.module.evaluate.domain.request.EvaluateQueryRequest;
import xin.marcher.module.evaluate.repository.EvaluateRepository;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 数据处理任务类
 */
@Slf4j
@Component
public class ItemCollectionCalSchedule {

    @Autowired
    private EvaluateRepository evaluateRepository;

    @Autowired
    private RedisLock redisLock;

    @XxlJob("itemScoreCal")
    public void itemScoreCal() {
        // 1.从原始数据表中分页获取初始数据，按照日期，每天调度一次，每天获取前一天的数据
        EvaluateQueryRequest request = new EvaluateQueryRequest();
        Date collectTime = new Date();

        String dateStr = DateFormatUtil.format(collectTime);
        try {
            collectTime = DateFormatUtil.StringToDate(dateStr);
            collectTime = DateUtils.addDays(new Date(), -1);
        } catch (Exception e) {
            log.error("日期格式转换错误！error:{}", e);
        }
        request.setCollectTime(collectTime);
        request.setDelFlag(YesOrNoEnum.YES.getCode());
        // 2.获取评分配置
        // 商品评分配置
        ItemScoreWeightConfigurationDO itemConfiguration = evaluateRepository.queryItemScoreWeight();
        // 店铺评分配置
        ShopScoreWeightConfigurationDO shopConfiguration = evaluateRepository.queryShopScoreWeight();
        // 如果没有
        if (Objects.isNull(itemConfiguration) || Objects.isNull(shopConfiguration)) {
            log.info("获取权重配置信息为空，商品权重配置：{},店铺权重配置：{}",
                    JSON.toJSONString(itemConfiguration),
                    JSON.toJSONString(shopConfiguration));
            return;
        }
        handleItemCollection(request, itemConfiguration, shopConfiguration);
    }

    @Transactional(rollbackFor = Exception.class)
    private void handleItemCollection(EvaluateQueryRequest request,
                                      ItemScoreWeightConfigurationDO itemConfiguration,
                                      ShopScoreWeightConfigurationDO shopConfiguration) {
        PageResult<ItemCollectionDO> page = null;
        // 循环不断获取数据，对数据进行评分计算处理
        while (true) {
            // 使用分布式锁，每次只能一个job获取到数据，获取到后直接设置获取到的数据为失效状态
            try {
                redisLock.lock(EvaluateRedisKeyConstants.evaluate_item_cal_lock, 10);
                // 获取一批数据
                page = evaluateRepository.queryByItemKey(request);
                if (Objects.isNull(page) || CollectionUtils.isEmpty(page.getContent())) {
                    break;
                }
                // 直接设置这批数据状态为失效状态
                List<Long> list = page.getContent().stream().map(ItemCollectionDO::getId)
                        .collect(Collectors.toList());
                evaluateRepository.invalidItemCollection(list);
            } catch (Exception e) {
                log.info("error:{}", e);
                throw new BaseBizException("获取数据失败");
            } finally {
                redisLock.unlock(EvaluateRedisKeyConstants.evaluate_item_cal_lock);
            }

            // 数据不为空，则对数据做评分计算相关的处理。
            // 2.对商品进行评分计算，包括商品评分，店铺评分，好评率，差评率，推荐指数等
            List<HandledItemCollectionDO> list = calScore(page.getContent(), itemConfiguration, shopConfiguration);

            // 如果没有ES，可以写入数据库，但是数据库的检索能力很差，无法实现高性能的商品检索，因此使用ES来存储处理后的商品数据
            // evaluateRepository.batchInsertHandledItem(list);
            // 3.处理后的商品信息写入ES
            evaluateRepository.batchInsertHandledItemToEs(list);
        }
    }

    /**
     * 处理商品数据，模拟算法模型，因此使用的一些计算常量没有提取到常量类中
     * 因为在这个算法模拟的过程中专门提取常量值没有实际意义
     *
     * @param list              原始数据list
     * @param itemConfiguration 商品评分权重
     * @param shopConfiguration 店铺评分权重
     * @return
     */
    private List<HandledItemCollectionDO> calScore(List<ItemCollectionDO> list,
                                                   ItemScoreWeightConfigurationDO itemConfiguration,
                                                   ShopScoreWeightConfigurationDO shopConfiguration) {
        List<HandledItemCollectionDO> handledItemCollectionDOS = new ArrayList<>(list.size());
        // 1.循环处理商品数据
        for (ItemCollectionDO itemCollectionDO : list) {
            // 关于销量评分的计算，我们使用分段线性函数来做，大致就是0-1000范围，为一个分段
            // 1000-5000为一个分段，5000-20000为一个分段，大于20000直接得分100分
            // 计算商品评分：销量、收藏、评论、好评率、差评率
            // 计算销量评分
            Integer sellCountScore = itemSellCountScore(itemCollectionDO.getSellCount());
            // 收藏量评分计算，也使用分短线性函数来做，大致就是0-1000为一个分短，
            // 计算收藏量评分
            Integer likeScore = itemCommentAndLikeCountScore(itemCollectionDO.getLikeCount());
            // 计算评论量评分
            Integer commentScore = itemCommentAndLikeCountScore(itemCollectionDO.getCommentCount());
            // 计算好评率及好评率评分
            Integer goodCommentRate = itemCollectionDO.getCommentCount() == 0 ? 0 : (itemCollectionDO.getGoodCommentCount() * 100) / itemCollectionDO.getCommentCount();
            Integer goodCommentScore = goodCommentRate < 90 ? 0 : goodCommentRate % 100;

            // 计算差评率及差评率评分
            Integer badCommentRate = itemCollectionDO.getCommentCount() == 0 ? 0 : (itemCollectionDO.getBadCommentCount() * 100) / itemCollectionDO.getCommentCount();
            Integer badCommentScore = badCommentRate > 10 ? 0 : (100 - badCommentRate) % 100;

            log.info("sellCountScore:{},likeScore:{},commentScore:{},goodCommentScore:{},badCommentScore:{}", sellCountScore, likeScore, commentScore, goodCommentScore, badCommentScore);
            // 计算商品评分，10分制，分数保留小数点后1位
            // 例如100->10，95 -> 9.5
            Integer itemScore = (sellCountScore * itemConfiguration.getSellCountWeight()
                    + likeScore * itemConfiguration.getLikeCountWeight()
                    + commentScore * itemConfiguration.getCommentCountWeight()
                    + goodCommentScore * itemConfiguration.getGoodCommentCountWeight()
                    + badCommentScore * itemConfiguration.getBadCommentCountWeight()) / 10;

            log.info("itemScore:{}", itemScore);
            // 计算店铺评分：店铺评价、店铺服务评价、店铺物流评价
            // 店铺评分默认保存为100以内的值，即9.5的评分，实际对应值为95
            // 除以10，是因为权重总和为10
            Integer shopScore = (itemCollectionDO.getShopDescScore() * shopConfiguration.getShopDescScoreWeight()
                    + itemCollectionDO.getShopServiceScore() * shopConfiguration.getShopServiceScoreWeight()
                    + itemCollectionDO.getShopLogisticsScore() * shopConfiguration.getShopLogisticsScoreWeight())
                    / 10;

            Integer recommendedLevel = (itemScore + shopScore) / 2;

            List<String> recommendedReason = new ArrayList<>(6);
            // 填充推荐理由，只有评分超过9.0才会填充推荐理由
            if (recommendedLevel >= 90) {
                // 当商品评分超过9.0才会填充商品相关的推荐理由
                if (itemScore > 90) {
                    if (sellCountScore >= 9) {
                        recommendedReason.add("销量高;");
                    }
                    if (goodCommentScore >= 9) {
                        recommendedReason.add("好评率高;");
                    }
                }
                // 当店铺评分超过9.0才会填充店铺相关的推荐理由
                if (shopScore >= 90) {
                    if (itemCollectionDO.getShopDescScore() >= 90) {
                        recommendedReason.add("店铺评价高;");
                    }
                    if (itemCollectionDO.getShopServiceScore() >= 90) {
                        recommendedReason.add("店铺服务好;");
                    }
                    if (itemCollectionDO.getShopLogisticsScore() >= 90) {
                        recommendedReason.add("店铺物流评价高");
                    }
                }
            }
            HandledItemCollectionDO handledItemCollectionDO = HandledItemCollectionDO.builder()
                    .itemId(itemCollectionDO.getItemId())
                    .itemKey(itemCollectionDO.getItemKey())
                    .itemName(itemCollectionDO.getItemName())
                    .categoryId(itemCollectionDO.getCategoryId())
                    .categoryName(itemCollectionDO.getCategoryName())
                    .price(itemCollectionDO.getPrice())
                    .discountPrice(itemCollectionDO.getDiscountPrice())
                    .collectTime(itemCollectionDO.getCollectTime())
                    .dataChannel(itemCollectionDO.getDataChannel())
                    .itemScore(itemScore)
                    .shopScore(shopScore)
                    .goodCommentRate(goodCommentRate)
                    .badCommentRate(badCommentRate)
                    .recommendedLevel(recommendedLevel)
                    .recommendedReason(JSON.toJSONString(recommendedReason))
                    .build();
            handledItemCollectionDO.initCommon();
            handledItemCollectionDOS.add(handledItemCollectionDO);
        }
        return handledItemCollectionDOS;
    }

    private Integer itemCommentAndLikeCountScore(Integer target) {
        Double coefficient = 0.0;
        if (target >= 8000) {
            coefficient = 10.0;
        }
        // 第一个区间范围内，以10的指数为系数
        if (target > 0 && target <= 1000) {
            coefficient = Math.log10(target);
        }
        // 第二个区间范围内，以上个区间最大系数3 + 第二段的系数
        // 例如3000 - 1000 = 2000 / 1000 = 2 2+3 = 5  3000销量时系数为5
        if (target > 1000 && target < 8000) {
            coefficient = 3.0 + (target - 1000.0) / 1000.0;
        }
        // 计算得分
        Double score = (coefficient * 10);
        return score.intValue();
    }

    /**
     * 获取商品评分系数
     * 因为是一个模拟算法，算法的区间范围的特殊值，提取出来作为常量的话，并不具备通用意义，因此此处并没有进行提取
     * 如果在真实的数据模型构建的过程中，参数一般都是一组可配置化的模型，不会直接写死
     *
     * @param target
     * @return
     */
    private Integer itemSellCountScore(Integer target) {
        Double coefficient = 0.0;
        if (target >= 20000) {
            coefficient = 10.0;
        }
        // 第一个区间范围内，以10的指数为系数
        if (target > 0 && target <= 1000) {
            coefficient = Math.log10(target);
        }
        // 第二个区间范围内，以上个区间最大系数3 + 第二段的系数
        // 例如3000 - 1000 = 2000 / 1000 = 2 2+3 = 5  3000销量时系数为5
        if (target > 1000 && target <= 5000) {
            coefficient = 3.0 + (target - 1000) / 1000;
        }
        // 第三个区间范围内，以上个区间最大系数7 + 第三段的系数
        // 例如10000  7 + 10000/ 10000 = 8 10000销量时，系数为8
        if (target > 5000 && target < 20000) {
            coefficient = 7.0 + (target / 10000);
        }
        // 计算得分
        Double score = (coefficient * 10);
        return score.intValue();
    }
}
